Merged [21103]: I'm sick of being prompted for my plugins... added a hidden preferenc...
[adiumx.git] / Frameworks / Adium Framework / Source / AICorePluginLoader.m
blobdc47f62ba4a27358fd79d589d6553dbe622d4558
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
18  Core - Plugin Loader
20  Loads external plugins (Including plugins stored within our application bundle).  Also responsible for warning the
21  user of old or incompatible plugins.
23  */
25 #import <Adium/AICorePluginLoader.h>
26 #import <AIUtilities/AIFileManagerAdditions.h>
27 #import <AIUtilities/AIApplicationAdditions.h>
28 #import <Adium/AIPlugin.h>
30 #define DIRECTORY_INTERNAL_PLUGINS              [@"Contents" stringByAppendingPathComponent:@"PlugIns"] //Path to the internal plugins
31 #define EXTERNAL_PLUGIN_FOLDER                  @"PlugIns"                              //Folder name of external plugins
32 #define EXTERNAL_DISABLED_PLUGIN_FOLDER @"PlugIns (Disabled)"   //Folder name for disabled external plugins
33 #define EXTENSION_ADIUM_PLUGIN                  @"AdiumPlugin"                  //File extension of a plugin
35 #define CONFIRMED_PLUGINS                               @"Confirmed Plugins"
36 #define CONFIRMED_PLUGINS_VERSION               @"Confirmed Plugin Version"
38 @interface AICorePluginLoader (PRIVATE)
39 - (void)loadPlugins;
40 + (BOOL)confirmPluginAtPath:(NSString *)pluginPath;
41 + (void)disablePlugin:(NSString *)pluginPath;
42 @end
44 @implementation AICorePluginLoader
46 - (id)init
48         if ((self = [super init])) {
49                 pluginArray = [[NSMutableArray alloc] init];
50                 
51                 [self loadPlugins];
52         }
54         return self;
57 //init
58 - (void)loadPlugins
60         //Init
61         [adium createResourcePathForName:EXTERNAL_PLUGIN_FOLDER];
63         //If the Adium version has changed since our last run, warn the user that their external plugins may no longer work
64         NSString        *lastVersion = [[NSUserDefaults standardUserDefaults] objectForKey:CONFIRMED_PLUGINS_VERSION];
65         if (![[NSApp applicationVersion] isEqualToString:lastVersion]) {
66                 [[NSUserDefaults standardUserDefaults] removeObjectForKey:CONFIRMED_PLUGINS];
67                 [[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:CONFIRMED_PLUGINS_VERSION];
68         }
70         NSEnumerator    *enumerator = [[adium allResourcesForName:EXTERNAL_PLUGIN_FOLDER
71                                                                                            withExtensions:EXTENSION_ADIUM_PLUGIN] objectEnumerator];
72         NSString                *path;
74         //Load any external plugins the user has installed
75         while ((path = [enumerator nextObject])) {
76                 [[self class] loadPluginAtPath:path confirmLoading:YES pluginArray:pluginArray];
77         }
78         
79         NSString *internalPluginsPath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DIRECTORY_INTERNAL_PLUGINS] stringByExpandingTildeInPath];
80         //Load the plugins in our bundle
81         enumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:internalPluginsPath] objectEnumerator];
82         while ((path = [enumerator nextObject])) {
83                 if ([[path pathExtension] caseInsensitiveCompare:EXTENSION_ADIUM_PLUGIN] == 0)
84                         [[self class] loadPluginAtPath:[internalPluginsPath stringByAppendingPathComponent:path]
85                                                         confirmLoading:NO
86                                                            pluginArray:pluginArray];
87         }
90 - (void)controllerDidLoad
94 //Give all external plugins a chance to close
95 - (void)controllerWillClose
97     NSEnumerator        *enumerator = [pluginArray objectEnumerator];
98     id <AIPlugin>       plugin;
100     while ((plugin = [enumerator nextObject])) {
101                 [[adium notificationCenter] removeObserver:plugin];
102                 [[NSNotificationCenter defaultCenter] removeObserver:plugin];
103                 [plugin uninstallPlugin];
104     }
107 - (void)dealloc
109         [pluginArray release];
110         pluginArray = nil;
112         [super dealloc];
116  * @brief Load plugins from the specified path
118  * @param pluginPath The path to the plugin bundle
119  * @param confirmLoading If YES, confirm loading of the plugin if it hasn't been loaded with this Adium version before
120  * @param inPluginArray May be nil.  If non-nil, an NSMutableArray to fill with an instance of the principal class (AIPlugin conforming) of each plugin which loads.
121  */
122 + (void)loadPluginAtPath:(NSString *)pluginPath confirmLoading:(BOOL)confirmLoading pluginArray:(NSMutableArray *)inPluginArray
124         BOOL                    loadPlugin = YES;
125         
126         //Confirm the presence of external plugins with the user
127         if (confirmLoading) {
128                 loadPlugin = [self confirmPluginAtPath:pluginPath];
129         }
130                 
131         //Load the plugin
132         if (loadPlugin) {
133                 NSBundle                *pluginBundle;
134                 id <AIPlugin>   plugin = nil;
136                 @try
137                 {
138                         if ((pluginBundle = [NSBundle bundleWithPath:pluginPath])) {
139                         Class principalClass = [pluginBundle principalClass];
140                         if (principalClass) {
141                                 plugin = [[principalClass alloc] init];
142                         } else {
143                                 NSLog(@"Failed to obtain principal class from plugin \"%@\" (\"%@\")! infoDictionary: %@",
144                                           [pluginPath lastPathComponent],
145                                           pluginPath,
146                                           [pluginBundle infoDictionary]);
147                         }
148                         
149                         if (plugin) {
150                                 [plugin installPlugin];
151                                 [inPluginArray addObject:plugin];
152                                 [plugin release];
153                         } else {
154                                 NSLog(@"Failed to initialize Plugin \"%@\" (\"%@\")!",[pluginPath lastPathComponent],pluginPath);
155                         }
156                         } else {
157                                 NSLog(@"Failed to open Plugin \"%@\"!",[pluginPath lastPathComponent]);
158                         }
159                 }
160                 @catch(id exc)
161                 {
162                         if (confirmLoading) {
163                                 //The plugin encountered an exception while it was loading.  There is no reason to leave this old
164                                 //or poorly coded plugin enabled so that it can cause more problems, so disable it and inform
165                                 //the user that they'll need to restart.
166                                 [self disablePlugin:pluginPath];
167                                 NSRunCriticalAlertPanel([NSString stringWithFormat:@"Error loading %@",[[pluginPath lastPathComponent] stringByDeletingPathExtension]],
168                                                                                 @"An external plugin failed to load and has been disabled.  Please relaunch Adium",
169                                                                                 @"Quit",
170                                                                                 nil,
171                                                                                 nil);
172                                 [NSApp terminate:nil];                                  
173                         }
174                 }
175         }
178 //Confirm the presence of an external plugin with the user.  Returns YES if the plugin should be loaded.
179 + (BOOL)confirmPluginAtPath:(NSString *)pluginPath
181         BOOL    loadPlugin = YES;
182         NSArray *confirmed = [[NSUserDefaults standardUserDefaults] objectForKey:CONFIRMED_PLUGINS];
184         if (![[NSUserDefaults standardUserDefaults] boolForKey:@"AIAutoConfirmExternalPlugins"]  &&
185                 (!confirmed || ![confirmed containsObject:[pluginPath lastPathComponent]])) {
186                 if (NSRunInformationalAlertPanel([NSString stringWithFormat:@"Disable %@?",[[pluginPath lastPathComponent] stringByDeletingPathExtension]],
187                                                                                 @"External plugins may cause crashes and odd behavior after updating Adium.  Disable this plugin if you experience any issues.",
188                                                                                 @"Disable", 
189                                                                                 @"Continue",
190                                                                                 nil) == NSAlertDefaultReturn) {
191                         //Disable this plugin
192                         [self disablePlugin:pluginPath];
193                         loadPlugin = NO;
194                         
195                 } else {
196                         //Add this plugin to our confirmed list
197                         confirmed = (confirmed ? [confirmed arrayByAddingObject:[pluginPath lastPathComponent]] : [NSArray arrayWithObject:[pluginPath lastPathComponent]]);
198                         [[NSUserDefaults standardUserDefaults] setObject:confirmed forKey:CONFIRMED_PLUGINS];
199                 }
200         }
201         
202         return loadPlugin;
205 //Move a plugin to the disabled plugins folder
206 + (void)disablePlugin:(NSString *)pluginPath
208         NSString        *pluginName = [pluginPath lastPathComponent];
209         NSString        *basePath = [pluginPath stringByDeletingLastPathComponent];
210         NSString        *disabledPath = [[basePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:EXTERNAL_DISABLED_PLUGIN_FOLDER];
211         
212         [[NSFileManager defaultManager] createDirectoriesForPath:disabledPath];
213         [[NSFileManager defaultManager] movePath:[basePath stringByAppendingPathComponent:pluginName]
214                                                                           toPath:[disabledPath stringByAppendingPathComponent:pluginName]
215                                                                          handler:nil];
218 @end